vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Woche 2

Tag 11

Subroutinen erstellen und verwenden

Wir vervollständigen Ihr Wissen über den Perl-Sprachkern heute mit dem Thema Subroutinen, Funktionen und lokale Variablen. Wenn Sie bestimmten Code immer wieder verwenden, müssen Sie ihn nicht jedesmal in Ihr Skript tippen - Sie können ihn in eine Subroutine packen und diese, wann immer Sie sie brauchen, aufrufen - genau wie Sie es von Perl-Funktionen kennen.

Heute erkläre ich Ihnen unter anderem:

Subroutinen und Funktionen

Eine Funktion ist im allgemeinen ein Stück Code, das auf irgendwelchen Daten einen oder mehrere Vorgänge ausführt. Man ruft eine Funktion auf, indem man im Skript ihren Namen nennt und ihr Argumente übergibt. Wenn Perl das Skript ausführt und auf einen Funktionsaufruf stößt, wechselt Perl zur entsprechenden Funktionsdefinition, führt die dortigen Anweisungen aus und kehrt dann an die Stelle zurück, an der es das Skript verlassen hat. Das gilt für alle Arten von Funktionen.

Wir haben in den bisherigen Kapiteln bereits viel mit Perls eigenen Funktionen wie print, sort, keys, chomp etc. gearbeitet. Diese sind in Perls Standardbibliothek definiert, so dass sie in allen Perl-Programmen zur Verfügung stehen. Eine zweite Art von Funktionen sind in zusätzlichen Perl-Modulen oder -Bibliotheken definiert. Um eine solche Funktion aufrufen zu können, müssen Sie am Anfang Ihres Skripts das zugehörige Modul geladen haben - wie das geht, zeige ich Ihnen am Tag 13.

Eine dritte Art von Funktionen sind die Subroutinen, die Sie selbst definieren. Die Begriffe Funktion und Subroutine bedeuten in Perl im Grunde das gleiche.1 Manche Programmierer nennen Subroutinen auch benutzerdefininierte Funktionen, um sie von Perls eigenen Funktionen zu unterscheiden, in anderen Zusammenhängen ist eine Unterscheidung gar nicht nötig. Ich nenne in diesem Buch die Funktionen, die Sie in Ihren eigenen Programmen (und später auch Modulen) selbst definieren, Subroutinen. Funktionen nenne ich die, die Sie woanders herbekommen - aus der Perl- Standardbibliothek oder optionalen Modulen.

Was nützt Ihnen eine Subroutine? Sobald Sie mehr als nur ein paar Zeilen Code in Ihren Skripts wiederholen müssen, haben Sie einen guten Grund, diesen Code in eine Subroutine zu packen. Das erspart Ihnen Tipparbeit. Doch auch wenn Sie den Code nur einmal brauchen, kann es - besonders bei komplexen Aufgaben - von Nutzen sein, das Skript aufzuteilen und die verschiedenen Teilaufgaben in eigenen Subroutinen zu lösen. Klare Namen für die einzelnen Vorgänge (diagramm_ausgeben oder max_finden) steigern die Übersichtlichkeit Ihres Skripts. Eventuelle Probleme lassen sich schneller isolieren: Sie können jede Subroutine für sich allein schreiben, testen und überprüfen - und sicher sein, dass sie, wenn Sie sie schließlich in Ihr Gesamtskript einfügen, auch funktioniert, wie Sie erwarten. Subroutinen sind eine Frage des Programmierstils - je mehr Einzelprobleme Sie auch in eigenen Subroutinen lösen, desto übersichtlicher und nachvollziehbarer werden Ihre Skripts für Sie und andere.

Einfache Subroutinen definieren und aufrufen

Die einfachste Form von Subroutine nimmt keine Argumente entgegen, verwendet keine lokalen Variablen, gibt keinen Wert zurück - und hat nicht besonders viel Sinn. Am Beispiel einer solchen Subroutine zeige ich Ihnen im folgenden Abschnitt, wie Sie Subroutinen deklarieren, definieren und aufrufen.

Eine Beispielsubroutine

Nehmen wir ein ganz einfaches Beispiel. Erinnern Sie sich an das Skript temperatur.pl von Tag 2, das nach einer Temperatur in Fahrenheit gefragt und sie in Celsius umgerechnet hat? Die eigentliche Berechnung stand mitten im Skript, aber wir hätten sie auch in eine Subroutine stellen können:

1#!/usr/bin/perl -w

$fahr = 0;
$cel = 0;

print 'Geben Sie eine Temperatur in Fahrenheit ein ';
chomp ($fahr = <STDIN>);
&f2c(); #Fahrenheit zu Celsius
print "$fahr Grad Fahrenheit entsprechen ";
printf("%d Grad Celsius\n", $cel);

sub f2c {
$cel = ($fahr - 32) * 5 / 9;
}

Sehen Sie sich genau an, wie wir die Subroutine hier einsetzen. Perl führt das Skript wie gewohnt Zeile für Zeile aus, bis es zu dem Verweis auf eine Subroutine (hier &f2c) gelangt. Jetzt wechselt Perl zur Subroutinendefinition (den letzten Zeilen im Skript), führt den Block der Subroutine aus und kehrt dann dorthin zurück, wo die normale Ausführung unterbrochen wurde. In unserem Beispiel bedeutet dies, dass Perl, nachdem es von der Tastatur die Temperatur eingelesen hat, zur Subroutine &f2c wechselt, den Wert in Celsius umrechnet und dann das Ergebnis ausgibt.

Wenn ich sage, Perl wechselt zur Subroutine, meine ich nicht, dass Perl wirklich zu dieser Skriptzeile springt. Bevor Perl ein Skript ausführt, liest es das gesamte Skript in den Speicher und informiert sich über die Definitionen der Subroutinen. Später springt die Ausführung dann nur noch zu den Definitionen der Subroutinen und wieder zurück.

Subroutinen definieren

An diesem simplen Beispiel haben Sie gesehen, wie man Subroutinen definiert:

sub subroutinenname {
Anweisungen;
...
}

Eine Subroutinendefinition beginnt mit dem Wort sub, gefolgt vom Namen der Subroutine, gefolgt von einem Block. Der Block ist, wie Sie von Schleifen und Bedingungen her kennen, eine mit geschweiften Klammern umgebene Folge von Perl-Anweisungen. Ein Beispiel:

sub zahl_holen {
print 'Bitte eine Zahl eingeben: ';
chomp($zahl = <STDIN>);
}

Der Name einer Subroutine kann aus beliebig vielen alphanumerischen Zeichen und Unterstrichen bestehen, kommt nicht in Konflikt mit gleichnamigen Skalar-, Array- oder Hash-Variablen und unterscheidet zwischen Groß- und Kleinbuchstaben.

Subroutinendefinitionen können Sie überall dorthin stellen, wo auch eine normale Anweisung stehen kann - an den Anfang Ihres Skripts, ans Ende oder mitten hinein (sogar in andere Blöcke). Im allgemeinen empfiehlt es sich jedoch aus Gründen der Übersichtlichkeit, sie alle zusammen an den Anfang oder das Ende des Skripts zu setzen.

Subroutinen aufrufen

Mit einem kaufmännischen Und (&) vor dem Namen der Subroutine und optionalen runden Klammern danach rufen Sie eine Subroutine auf:

&f2c;   #Fahrenheit zu Celsius
&zahl_holen()

In die Klammern kommen die Argumente, die Sie der Subroutine übergeben (mehr dazu später).

Das kaufmännische Und (&) ist optional; Sie können Subroutinen auch ohne & aufrufen. Einige Programmierer verwenden das &, weil es die Unterscheidung von Perl-eigenen Funktionen und selbst definierten (oder aus Modulen importierten) Subroutinen erleichtert. Ich rufe in diesem Buch Subroutinen immer mit & auf.

Es gibt einen Ausnahmefall, in dem das kaufmännische Und nicht optional ist: Wenn Sie sich indirekt auf eine Subroutine beziehen, sie aber nicht aufrufen, also zum Beispiel mit defined überprüfen, ob die Subroutine definiert ist, dann müssen Sie das & verwenden.

Außerdem könnten Sie in einigen Fällen auch die Klammern weglassen (insbesondere, wenn die Subroutine an einer früheren Stelle im Skript oder importierten Modul bereits deklariert wurde). Über die Deklaration von Subroutinen erfahren Sie im Vertiefungsabschnitt mehr. Ich verwende die Klammern in jedem Fall, um Verwirrung zu vermeiden - denn verkehrt sind sie nie.

Sie können eine Subroutine nicht nur aus dem Hauptkörper Ihres Skripts aufrufen, sondern auch aus anderen Subroutinen - die Sie vielleicht aus wieder anderen Subroutinen aufgerufen haben. In Perl können Sie Subroutinenaufrufe so tief verschachteln, wie Ihr Speicher erlaubt. Auf ein paar Dinge wie die Argumente oder die Gültigkeitsbereiche der lokalen Variablen müssen Sie dabei noch achten, aber dazu kommen wir noch.

Ein Beispiel: Statistik in Subroutinen

Mehr oder weniger zum Spaß habe ich mir die letzte Version unseres Statistikskripts geschnappt, in seine Einzelteile zerlegt und jedes in eine Subroutine gepackt. Im Hauptkörper des Skripts werden jetzt nur noch Subroutinen aufgerufen. Am Verhalten des Skripts hat sich nichts geändert, nur an seinem Aufbau. Das Ergebnis können Sie in Listing 11.1 bewundern.

Listing 11.1: Das Skript statsfunk.pl

1:  #!/usr/bin/perl -w
2:
3: &initvars();
4: &getinput();
5: &printresults();
6:
7: sub initvars {
8: $input = ""; # temp Input
9: @nums = (); # Array: Zahlen
10: %freq = (); # Hash: Zahl-Haeufigkeit
11: $maxfreq = 0; # hoechste Haeufigkeit
12: $count = 0; # Anzahl Zahlen
13: $sum = 0; # Summe
14: $avg = 0; # Durchschnitt
15: $med = 0; # Median
16: @keys = (); # temp keys
17: $totalspace = 0; # gesamte Breite des Histogramms
18: }
19:
20: sub getinput {
21: while (defined ($input = <>)) {
22: chomp ($input);
23: $nums[$count] = $input;
24: $freq{$input}++;
25: if ($maxfreq < $freq{$input}) { $maxfreq = $freq{$input} }
26: $count++;
27: $sum += $input;
28: }
29:
30: }
31:
32: sub printresults {
33: @nums = sort { $a <=> $b } @nums;
34:
35: $avg = $sum / $count;
36: $med = $nums[$count /2];
37:
38 print "\nAnzahl der eingegebenen Zahlen: $count\n";
39: print "Summe der Zahlen: $sum\n";
40: print "Kleinste Zahl: $nums[0]\n";
41: print "Groesste Zahl: $nums[$#nums]\n";
42: printf("Durchschnitt: %.2f\n", $avg);
43: print "Mittelwert: $med\n\n";
44: &printhist();
45: }
46:
47: sub printhist {
48: @keys = sort { $a <=> $b } keys %freq;
49:
50: for ($i = $maxfreq; $i > 0; $i--) {
51: foreach $num (@keys) {
52: $space = (length $num);
53: if ($freq{$num} >= $i) {
54: print( (" " x $space) . "*");
55: } else {
56: print " " x (($space) + 1);
57: }
58: if ($i == $maxfreq) { $totalspace += $space + 1; }
59: }
60: print "\n";
61: }
62: print "-" x $totalspace;
63: print "\n @keys\n";
64: }

Da diese Version des Skripts eigentlich nichts anderes macht als die letzte, gibt es hier nur ein paar Dinge anzumerken:

Aus Subroutinen Werte zurückgeben

Mit einem Aufruf der Subroutine allein, als einziger Anweisung in einer Zeile, wird die Subroutine ausgeführt - und das war's dann auch schon. Viel nützlicher sind Subroutinen, die einen Wert zurückgeben: Sie können sie in Ausdrücke oder Anweisungen einbauen oder als Argument an andere Subroutinen übergeben.

Wir haben schon besprochen, dass ein Block das Ergebnis der letzten Auswertung zurückgibt. Der Block, der eine Subroutine definiert, macht da keinen Unterschied. So liest zum Beispiel das folgende Skript zwei Zahlen ein und addiert sie:

$sum = &sumnums();
print "Summe: $sum\n";

sub sumnums { # Zahlen addieren
print 'Geben Sie eine Zahl ein: ';
chomp($zahl1 = <STDIN>);
print 'Geben Sie eine weiter Zahl ein: ';
chomp($zahl2 = <STDIN>);
$zahl1 + $zahl2;
}

In der ersten Zeile haben wir hier einen Subroutinenaufruf auf der rechten Seite einer Zuweisung. Was wir hier zuweisen ist nicht etwa die Subroutine selbst (wie sollte das aussehen?), sondern ihren Rückgabewert. Das Ergebnis der Addition ($zahl1 + $zahl2) in der letzten Zeile der Subroutine &sumnums ist der Wert, den die Subroutine zurückgibt, der an $sum zugewiesen und von print ausgegeben wird.

Der Haken an diesem Verhalten ist, dass die letzte Anweisung im Block zwar häufig, aber nicht immer den Wert liefert, den die Subroutine zurückgeben soll. Die Regel besagt, dass ein Block das Ergebnis der letzten Auswertung zurückgibt - und was zuletzt ausgewertet wird, muss nicht immer die letzte Anweisung; es könnte zum Beispiel auch die Schleifenbedingung einer while- oder for-Schleife oder ein Schleifensteuerbefehl sein.

Weil der Rückgabewert einer Subroutine nicht immer klar ist, empfiehlt es sich, in der letzten Zeile mit return (zurückgeben) explizit festzulegen, was sie zurückgeben soll. Die Funktion return nimmt einen Ausdruck als Argument entgegen, wertet ihn aus und gibt das Ergebnis zurück. Die letzte Zeile von &sumnums sähe dann so aus:

return $zahl1 + $zahl2;

Sie möchten mehrere Werte aus einer Subroutine zurückgeben? Kein Problem. Geben Sie einfach eine Liste zurück (aber achten Sie im Hauptteil Ihres Skripts darauf, dass Sie mit dieser Liste richtig umgehen). Das folgende Code-Fragment zum Beispiel ruft eine Subroutine auf, die die Werte aus einem Array verarbeitet und eine Liste von drei Werten zurückgibt, die dann den Elementen $max, $min und $count zugewiesen werden (wie wir der Subroutine das Array @zahlen übergeben, soll uns hier noch nicht interessieren):

($max, $min, $count) = &berechne(@zahlen);
# ...
sub berechne {
# ...
return ($wert1, $wert2, $wert3);
}

Und wenn Sie mehrere Listen aus einer Subroutine zurückgeben möchten? Das geht nicht oder zumindest nicht so einfach. Die Funktion return löst in einem Listenargument alle eventuellen Unterlisten und Hashes in ihre Elemente auf - sie interpoliert Arrays und Hashes in eine lineare Liste von Skalaren. Dieses Verhalten ist keine Besonderheit von return, sondern Perls allgemeine »Datenliefermethode« von und an Subroutinen (mehr dazu später). Brauchen Sie von einer Subroutine wirklich mehrere Listen, müssen Sie dafür sorgen, dass sich die gelieferte lineare Liste außerhalb der Subroutine wieder in die Teillisten zurückverwandeln läßt.

Lokale Variablen in Subroutinen

Ein Perl-Skript in Subroutinen aufzuteilen, die mit denselben globalen Variablen arbeiten, ist im Grunde eine reine Codeformatierungsübung. Vom besseren Überblick einmal abgesehen, bringen Ihnen solche Subroutinen keine wesentlichen Vorteile. Unser Ziel jedoch sind Subroutinen als eigenständige Einheiten, die eigene Variablen haben, nur die ihnen übergebenen Argumenten bearbeiteten und die angeforderten Ergebnisse liefern. Solche Subroutinen sind leichter zu handhaben, wiederzuverwenden und zu debuggen. Diesem Ziel werden wir uns jetzt Schritt für Schritt annähern; und wir beginnen mit den lokalen Variablen.

Bisher hatten wir fast ausschließlich mit globalen Variablen zu tun. Global bedeutet, dass diese (Skalar-, Array- und Hash-) Variablen überall im Skript zur Verfügung stehen und so lange existieren, wie das Skript läuft. Sie haben zwar bereits ein paar Ausnahmen gesehen - zum Beispiel die Schleifenvariable von foreach oder die Treffervariablen in regulären Ausdrücken - doch größtenteils gehörten unsere Variablen dem globalen Gültigkeitsbereich an.

Lokale Variablen sind, in Verbindung mit Subroutinen, Variablen, die erst beim Start der Subroutine zu existieren beginnen, nur in der Subroutine zur Verfügung stehen und wieder verschwinden, sobald die Subroutine beendet ist. Ansonsten sind es ganz normale Variablen - sie sehen weder anders aus, noch werden sie anders verwendet als ihre globalen Pendants.

Sie haben in Perl zwei Möglichkeiten, lokale Variablen zu erstellen. Die einfachere behandeln wir hier, die andere (und worin sie sich unterscheidet) am Tag 13.

Zum Deklarieren einer lokalen Variablen für eine Subroutine setzen Sie den Operator my vor die Variable, wenn Sie sie initialisieren oder das erste Mal verwenden:

my $x = 1;  # $x ist jetzt lokal

Wenn Sie mehrere lokale Variablen deklarieren möchten, müssen Sie die Liste in Klammern setzen, sonst wirkt sich my nur auf die erste aus:2

my ($a, $b, $c);   # drei lokale Variablen, alle undefiniert.

Im vorigen Abschnitt haben wir eine Subroutine erstellt, die zwei Zahlen erfragt und addiert hat. Die beiden Variablen $zahl1 und $zahl2 waren global. Mit einer zusätzlichen Zeile machen wir sie zu lokalen Variablen:

$sum = &sumnums();
print "Summe: $sum\n";

sub sumnums {
my ($zahl1, $zahl2);
print 'Geben Sie eine Zahl ein: ';
chomp($zahl1 = <STDIN>);
print 'Geben Sie eine weiter Zahl ein: ';
chomp($zahl2 = <STDIN>);
return ($zahl1 + $zahl2);
}

In dieser Version deklariert die Zeile my ($zahl1, $zahl2) zwei lokale Variablen. Diese sind nur innerhalb der Subroutine sumnums existent und sichtbar.

Was passiert, wenn vor dem Subroutinenaufruf schon eine globale Variable $zahl1 vorhanden war? Perl hat damit kein Problem. Eine mit my deklarierte Variable verdeckt eine globale Variable gleichen Namens vor »ihrer Subroutine«. Oder andersherum formuliert: In einer Subroutine ist eine globale Variable unsichtbar, sobald in der Subroutine eine gleichnamige lokale Variable deklariert wird. Wenn Perl die Subroutine verläßt, verschwindet die my-Variable wieder, und Sie haben einen freien Blick (und Zugriff) auf die globale Variable. Weil dies - vor allem bei der Fehlersuche - sehr verwirren kann, ist es im allgemeinen eine gute Idee, Ihre lokalen Variablen anders zu nennen als Ihre globalen.

Über den Paketnamen kommen Sie in einer Subroutine aber auch an versteckte globale Variablen heran. Wir besprechen das an Tag 13.

Betrachten wir folgendes Beispiel:

#!/usr/bin/perl -w

$wert = 0;
print "vor subaufruf - \$wert ist $wert\n";
&subaufruf();
print "nach subaufruf - \$wert ist $wert\n";

sub subaufruf {
my ($wert);
$wert = 10;
print "innerhalb von subaufruf - \$wert ist $wert\n";
}

Dieses Skriptchen produziert folgende Ausgabe:

vor subaufruf - $wert ist 0
innerhalb von subaufruf - $wert ist 10
nach subaufruf - $wert ist 0

Sie sehen es selbst: Die lokale Variable $wert, die in der Subroutine deklariert wird, hat mit der globalen nichts zu tun. Sie in der Subroutine zu verändern, wirkt sich nicht im geringsten auf die globale Namensschwester aus.

Wenn wir jetzt aus subaufruf eine weitere Subroutine namens subaufruf2 aufrufen und dort wieder eine my-Variable $wert mit 0 initialisieren und inkrementieren würden, hätten wir drei völlig verschiedene Variablen namens $wert und folgende Ausgabe:

vor subaufruf - $wert ist 0
innerhalb von subaufruf - $wert ist 1
innerhalb von subaufruf2 - $wert ist 1
nach subaufruf - $wert ist 0

Auch wenn Sie Subroutinen ineinander verschachteln - eine Subroutine aus einer anderen Subroutine aufrufen -, sind die in der aufrufenden Subroutine deklarierten my- Variablen in der aufgerufenen Subroutine unsichtbar und umgekehrt.3 Hier unterscheidet sich Perl von vielen anderen Sprachen, die die Gültigkeitsbereiche von lokalen Variablen in verschachtelten Subroutinen »kaskadieren«. my-Variablen existieren in Perl wirklich einzig und allein in der Subroutine, in der sie deklariert werden, nirgendwo sonst.

Für die Computerexperten unter den Lesern heißt dies, dass die my-Variablen lexikalische statt dynamische Gültigkeit haben. Sie existieren nur innerhalb des Blocks, in dem sie definiert sind. Dazu gleich mehr.

Werte an Subroutinen übergeben

Jetzt wissen Sie, wie Subroutinen Werte zurückliefern und Daten in lokalen Variablen speichern. Fehlt nur noch, wie man ihnen Werte übergibt. Damit kommen wir auf die Argumente zu sprechen.

Argumente übergeben

Das Konzept der Datenübergabe an eine Subroutine (und zurück) ist in Perl ebenso schlicht wie ergreifend. Während Sie in anderen Sprachen schon beim Schreiben einer Subroutine genau festlegen müssen, wie viele Argumente welchen Typs sie entgegennehmen kann, packt bzw. expandiert Perl einfach alle Ihre Argumente in eine einzige lineare Liste. Bei skalaren Argumenten ist das keine besondere Aktion:

&meine_subroutine(1, 2, 3);

&meine_subroutine erhält in diesem Fall eine Liste von drei numerischen Werten. Achten Sie aber auf Listenargumente:

&meine_subroutine(@liste, @andere_liste);

In diesem Fall expandiert Perl die beiden Arrayvariablen und speichert alle ihre Elemente nacheinander in einer einzigen Liste - der Liste, die es an die Subroutine übergibt. Die Identität der Arrays geht in dieser Liste verloren (die Inhalte sind zwar noch alle da, aber wenn Sie nicht vorgesorgt haben, läßt sich nicht mehr herausfinden, aus welchem Array sie stammen).

Bei Hashes ist es ähnlich: Ein Hash wird (wie immer, wenn Sie ihn einer Liste zuweisen) in seine Bestandteile zerlegt. Seine Schlüssel und Werte werden der Liste hinzugefügt, die die Subroutine schließlich entgegennimmt.

Doch was, wenn Ihre Argumente wirklich aus mehreren Arrays oder Hashes bestehen sollen? Keine Panik, es gibt ein paar Möglichkeiten, Perls Datenübergabeverhalten zu umgehen. Eine Möglichkeit wäre, Ihre Arrays oder Hashes als globale Variablen zu speichern und sich in der Subroutine einfach auf sie zu beziehen. Mit ein paar vorbereitenden Tricks könnten Sie die Teillisten auch aus der Argumentliste rekonstruieren (zum Beispiel, indem Sie als Argument auch die Länge der Arrays mit übergeben). Die dritte und wahrscheinlich die beste Möglichkeit ist, die Argumente als Referenzen zu übergeben. Die Struktur der Arrays und Hashes bliebe dabei erhalten - mit Referenzen befassen wir uns an Tag 19.

Argumente in der Subroutine entgegennehmen

Okay, die Argumente wurden in einer linearen Liste an die Subroutine übergeben. Und nun? Wie kommen Sie vom Körper der Subroutine aus an diese Parameterliste heran?4

Die an eine Subroutine übergebene Liste (auch Parameterliste dieser Subroutine genannt) wird im lokalen Spezialarray @_ gespeichert. Auf dieses Array können Sie mit den bekannten Techniken zugreifen. @_ ist eine lokale Variable der Subroutine, das heißt ihr Inhalt ist nur innerhalb der Subroutine sichtbar. Wenn Sie aus einer Subroutine heraus eine andere aufrufen, erhält diese zweite Subroutine ihr eigenes @_- Array.

Hier ein Beispiel einer Subroutine, die zwei Argumente addiert:

&addiere(2,3);  #Aufruf der Subroutine

sub addiere {
return $_[0] + $_[1];
}

Die beiden Argumente an die Subroutine - hier 2 und 3 - landen im Array @_. Mit den Ausdrücken $_[0] und $_[1] greifen Sie auf die ersten beiden Elemente dieses Arrays zu. Genau wie $foo[0] und $foo sich auf unterschiedliche Dinge beziehen, ist auch $_[0] etwas anderes als $_ ($_[0] ist das erste Element in der Argumentliste der Subroutine, $_ ist die Default-String-Spezialvariable).

Diese Subroutine ist nicht gerade intelligent - sie addiert lediglich die ersten beiden Argumente, ganz egal, wie viele Sie ihr übergeben haben. Weil Sie nicht kontrollieren können, wie viele Argumente eine Subroutine entgegennehmen kann, müßten Sie beim Aufruf auf die korrekte Anzahl achten oder in der Subroutine die Zahl der Argumente (also der Elemente in @_ ) überprüfen oder die Subroutine so umschreiben, dass sie etwas großzügiger mit mehreren Argumenten umgeht. Sie könnten sie zum Beispiel alle Argumente addieren lassen, wie viele es auch sein mögen:

sub addiere {
my $sum = 0;
foreach $i (@_) {
$sum += $i;
}

In den neueren Perl-Versionen (seit Release 5.003) haben Sie die Möglichkeit, bei der Deklaration einer Subroutine mit sogenannten Prototypen auch Art und Anzahl der Argumente zu bestimmen. Da dieses Feature relativ neu ist, möchten Sie sich in Ihren Skripts aber vielleicht nicht darauf verlassen. Mehr zu Prototypen auf Seite 332.

Perl hat keine eigene Methode zum expliziten Benennen von eingehenden Argumenten, aber ein sehr gebräuchlicher »Trick« macht praktisch das gleiche: Weisen Sie als erstes die Argumente von @_ lokalen Variablen zu:

sub foo {
my($max, $min, $inc) = @_;
...
}

Dann können Sie sich auf die Argumente mit einem leichter zu merkenden Variablennamen beziehen und die Positionen der einzelnen Argumente im Array @_ getrost vergessen. Eine andere (sehr gebräuchliche) Möglichkeit führt zum gleichen Ergebnis: Erinnern Sie sich an die Funktion shift? Bequemerweise entfernt shift, wenn es ohne Argumente innerhalb einer Subroutine aufgerufen wird, das erste Element von @_ und gibt es zurück (pop macht dasselbe mit dem letzten Element von @_):

sub foo {
my $max = shift;
my $min = shift;
my $inc = shift;
...
}

Eine Anmerkung zu den Werten in @_

Die Werte von @_ sind sogenannte Referenzparameter: Sie verweisen implizit auf die eigentlichen skalaren Argumente, die von außen übergeben wurden. Das bedeutet, dass, wenn Sie direkt im Array @_ ein Element verändern, unter Umständen auch einen Wert außerhalb der Subroutine verändern. Betrachten wir am besten, was folgendes Beispiel ausgibt:

#!/usr/bin/perl -w

$a = "a";
@b = ($a, 0);
%c = ('rom', '1', 'berlin', '2');

print "\n vor subaufruf: \$a = $a \@b = @b \%c = ";
foreach (keys %c) {
print "$_ : $c{$_} ";
}
&subaufruf($a, 10, @b, %c,);

print "\n nach subaufruf: \$a = $a \@b = @b \%c = ";
foreach (keys %c) {
print "$_ : $c{$_} ";
}
sub subaufruf {
my ($a, $b, $c, $d, $e ) = @_;
print "\n start subaufruf: \@ = @_ ";
$a = "my a";
$b = 20;
$c = "my b1";
$_[0] = "AAA";
$_[2] = B1;
$_[3] = B2;
$_[4] = "PARIS";
print "\n ende subaufruf: \$a = $a \$b = $b \$c = $c \n";
print " \@ = @_";
}

Dieser Code produziert folgenden Output (ohne dass wir mit subaufruf etwas zurückgeben):

 vor subaufruf:  $a = a   @b = a 0   %c = berlin : 2  rom : 1  
start subaufruf: @ = a 10 a 0 berlin 2 rom 1
ende subaufruf: $a = my a $b = 20 $c = my b1
@ = AAA 10 B1 B2 PARIS 2 rom 1
nach subaufruf: $a = AAA @b = B1 B2 %c = berlin : 2 rom : 1

Hier sehen Sie, wie die globalen Variablen durch die Zuweisungen an $_[0] etc. verändert werden. Es ist, als stünden die Variablen selbst in der Parameterliste @_ - so etwas nennt man dann Referenzübergabe. Nur auf den Hash haben wir keinen Einfluß ($_[4] = "PARIS" wirkt sich nicht auf %c aus), wir erhalten lediglich die aktuellen Werte seiner Elemente (ja, die Werte der Schlüssel und die Werte der Werte). So etwas nennt man dann Wertübergabe.

Am Anfang von subaufruf weisen wir @_ einer my-Liste von lokalen Variablen zu.5 Weil wir hier im Grunde eine Kopie der Parameterwerte machen, berühren wir keine der Originalvariablen, wenn wir die (Kopien in den) lokalen Variablen verändern - das ist die einfachere, verständlichere und empfehlenswerte Art des Umgangs mit unseren Argumenten.

Subroutinen und Kontext

Parameterübergabe, Subroutinendefinition, Rückgabewerte - all dies sind auch in anderen Sprachen wichtige Fragen bei der Definition von Funktionen. Aber das hier ist Perl; es muss noch ein paar Eigenar(tigkei)ten geben. Sie wissen bereits, dass Arrays und Hashes in einer Subroutinen-Parameterliste ihre Identität verlieren, weil sie aufgedröselt werden. Eine andere Eigenart von Perl ist der Kontext.

Da Subroutinen sowohl in Skalar- als auch in Listenkontext aufgerufen werden und sowohl einen Skalar als auch eine Liste zurückgeben können, spielt die Frage nach dem Kontext auch beim Definieren von Subroutinen eine Rolle - oder zumindest beim Aufrufen von Subroutinen: Rufen Sie Subroutinen, die Listen zurückgeben, nicht in Skalarkontext auf, wenn Sie nicht genau wissen, zu welchen Ergebnissen das führt.

Vielleicht möchten Sie aber gerade eine Subroutine schreiben, die sich je nach Kontext, in dem sie aufgerufen wird, unterschiedlich verhält. Dann hat Perl eine nette Funktion für Sie: wantarray. Die Funktion wantarray gibt wahr zurück, wenn die momentan laufende Subroutine in Listenkontext aufgerufen wurde, falsch wenn in Skalarkontext (»wantlist«, »will Liste« wäre ein passenderer Name, aber aus historischen Gründen heißt sie eben wantarray). So können Sie zum Beispiel vom Kontext abhängig machen, welchen Wert Sie aus einer Subroutine zurückgeben:

sub skalar_oder_liste {
# ...
if (wantarray()) {
return @liste;
} else { return $skalar}
}

Gehen Sie mit diesem Feature aber vorsichtig um. Bedenken Sie, welche Verwirrung schon Perl-Funktionen stiften können, die sich je nach Kontext unterschiedlich verhalten (und so manchen Blick in die Dokumentation erfordern). In vielen Fällen ist es besser, einen der Subroutine selbst entsprechenden Wert zurückzugeben und mit diesem in der Anweisung, die die Subroutine aufgerufen hat, entsprechend umzugehen.

Ein weiteres Beispiel: Statistik mit Menüführung

Verändern wir zum zweiten Mal in dieser Lektion unser gutes altes Statistikbeispiel und schöpfen wir dabei aus dem gesamten Wissen, das Sie im Laufe dieses Buches angehäuft haben. Diesmal soll das Skript nicht alles hintereinander weg ausführen, sondern die verschiedenen Operationen erst einmal in einem Menü anzeigen. Sie haben dann die Wahl, welche dieser Operationen Sie auf den aus Ihrer Input-Datei gelesenen Zahlen ausführen möchten.

Anders als im Skript mehrnamen.pl vom Tag 8 steuern wir dieses Menü hier aber nicht über eine große while-Schleife und diversen Bedingungen, sondern über Subroutinen. Die verschiedenen Berechnungen verschieben wir in die Subroutinen, in denen sie gebraucht werden.

Weil dieses Skript ziemlich lang ist, zeige ich Ihnen diesmal nicht zuerst den kompletten Code und bespreche ihn dann hinterher, sondern ich fange mit den Erklärungen der einzelnen Schritte an und stelle das vollständige Listing ans Ende dieses Abschnitts.

Beginnen wir mit dem Hauptkörper des Skripts. In unseren bisherigen Versionen mussten wir erst einmal eine Menge Variablen initialisieren - globale Variablen. Diesmal ist unser Initialsierungsteil nur noch zwei Zeilen lang, denn wir brauchen nur zwei globale Variablen. Eine für den Input (die Zahlen, die wir aus der angegeben Datei lesen) und eine, mit der wir stets im Blick haben, ob das Skript beendet werden soll oder nicht. Alle anderen deklarieren wir in den Subroutinen als lokale Variablen:

#!/usr/bin/perl -w

@zahlen = (); #Input aus Datei, eine Zahl pro Zeile
$choice = "";
&getinput();

Das Skript beginnt mit dem Aufruf der Subroutine &getinput. Diese Subroutine liest die Daten aus der Eingabedatei und speichert sie im Array @nums. Diese Version von getinput ist wesentlich kürzer als die letzte - wir kümmern uns hier nicht um die Häufigkeit der einzelnen Werte, ihre Gesamtsumme etc., und wir verwenden $_ anstatt einer temporären Input-Variablen. Das einzige, was wir außer dem Einlesen hier noch erledigen, ist das Sortieren der Zahlen im Array:

sub getinput {
my $count = 0;
while (<>) {
chomp;
$nums[$count] = $_;
$count++;
}
@nums = sort { $a <=> $b } @nums;
}

Nachdem wir das Input gespeichert haben, kommen wir zum Kern unseres menügesteuerten Skripts, einer while-Schleife, die für jede gewählte Menüoption jeweils die entsprechende Subroutine aufruft:

&getinput();
while ($choice !~ /q/i) {
$choice = &printmenu();
SWITCH: {
$choice =~ /^1/ && do {
&printdata(); last SWITCH; };
$choice =~ /^2/ && do {
&countsum (); last SWITCH; };
$choice =~ /^3/ && do {
&maxmin(); last SWITCH; };
$choice =~ /^4/ && do {
&meanmed(); last SWITCH; };
$choice =~ /^5/ && do {
&printhist(); last SWITCH; };
}
}

Schauen Sie! Ein Switch! Hier »simulieren« wir mit Pattern Matching, do-Anweisungen und dem Label (SWITCH) eine switch-Anweisung. Bei jedem möglichen Wert von $choice, 1 bis 5, rufen wir eine andere Subroutine auf und verlassen mit last SWITCH den gesamten Block. Beachten Sie, dass ein Block mit einem Label zwar keine Schleife ist, Sie ihn aber mit den Schleifensteuerbefehlen genauso verlassen können.

Welchen Wert $choice in dem SWITCH-Block überhaupt hat, bringen wir vorher mit dem Subroutinenaufruf &printmenu in Erfahrung. Beachten Sie, dass die while- Schleife immer wieder das Menü ausgibt und die gewählte Operation ausführt - bis printmenu ein q zurückliefert. Dann bricht die Schleife ab, und das Skript wird beendet.

Die Subroutine printmenu zeigt einfach die einzelnen Optionen an, liest und überprüft den vom Benutzer eingegebenen Wert und liefert diesen zurück:

sub printmenu {
my $in = "";
print "Was moechten Sie ausgeben? (Beenden mit Q): \n";
print "1. eine Liste der Zahlen \n";
print "2. die Anzahl und Summe der Zahlen\n";
print "3. die kleinste und die groesste Zahl\n";
print "4. den Durchschnitts- und den Medianwert\n";
print "5. ein Diagramm, wie oft jede Zahl vorkommt.\n";
while () {
print "\nIhre Auswahl --> ";
chomp($in = <STDIN>);
if ($in =~ /^\d$/ || $in =~ /^q$/i) {
return $in;
} else {
print "Ungueltige Eingabe. 1-5 oder Q, bitte.\n";
}
}
}

Gehen wir die im Menü gezeigten Optionen von oben nach unten durch. Möchte der Benutzer sich eine Liste der Zahlen anzeigen lassen, gibt er eine 1 ein, $choice erhält den Wert eins, und der SWITCH-Block ruft &printdata auf. Die Subroutine printdata durchläuft einfach das (globale) Array @nums und gibt die Elemente aus. Weil wir es aber übersichtlich lieben, möchten wir nach jeweils zehn Zahlen eine neue Zeile beginnen. Deshalb verfolgen wir in einer lokalen Variablen $i die Anzahl der Elemente, und wenn sie bei 10 angelangt ist, geben wir einen Zeilenvorschub aus und setzen sie zurück auf 1:

sub printdata {
my $i = 1;
print "die Zahlen: \n";
foreach $num (@nums) {
print "$num ";
if ($i == 10) {
print "\n";
$i = 1;
} else { $i++; }
}
print "\n\n";
}

Die zweite Menüoption ist die Ausgabe von Anzahl und Gesamtsumme unserer Zahlen beziehungsweise der Aufruf der Subroutine &countsum:

sub countsum {
print "Anzahl der Zahlen: ", scalar(@nums), "\n";
print "Summe der Zahlen: ", &sumnums(),"\n\n";
}

Diese Subroutine wiederum ruft &sumnums() auf, die die Summe aller Zahlen berechnet:

sub sumnums {
my $sum = 0;
foreach $num (@nums) {
$sum += $num;
}
return $sum;
}

In den bisherigen Versionen des Skripts haben wir die Summe gleich beim Einlesen der Zahlen berechnet, hier hingegen machen wir daraus einen separaten Vorgang. Sie könnten einwenden, dass das weniger effizient ist, zumal wir die Summe ja auch zur Berechnung des Durchschnittswerts brauchen - doch andererseits sparen wir uns dadurch Arbeit, bis sie wirklich nötig ist.

Die dritte Möglichkeit ist die Ausgabe der kleinsten und der größten Zahl. Hierfür müssen wir gar nicht viel herumrechnen - da unser Array sortiert ist, finden wir kleinstes und größtes Element am Anfang bzw. am Ende:

sub maxmin {
print "Kleinste Zahl: $nums[0]\n";
print "Groesste Zahl: $nums[$#nums]\n\n";
}

Entscheidet der Benutzer sich für die vierte Option, ermitteln wir Durchschnittswert und Median wie in den bisherigen Versionen des Skripts (nur dass wir uns die Summe von einer Subroutine liefern lassen):

sub meanmed {
printf("Durchschnitt: %.2f\n", &sumnums() / scalar(@nums));
print "Median: $nums[@nums / 2]\n\n";
}

Bleibt nur noch die fünfte Möglichkeit, das Histogramm auszugeben. Wie in den bisherigen Versionen ist dies der komplexeste Teil des Skripts. Hier allerdings packen wir alles, was mit dem Histogramm zu tun hat, in eine Subroutine printhist, anstatt die nötigen Werte über das gesamte Skript zu verteilen. Das bedeutet zum einen mehr lokale Variablen und Rechenarbeit als für die anderen Subroutinen. Zum anderen wird aber der Einleseprozeß nicht von irgendwelchen Häufigkeitsberechnungen verlangsamt, die am Ende vielleicht gar niemand braucht - und wir haben nicht während des gesamten Skriptablaufs den Häufigkeits-Hash im Speicher hängen:

sub printhist {
my %freq = ();
my $maxfreq = 0;
my @keys = ();
my $space = 0;
my $totalspace = 0;
my $num;
my $i;

# freq-Hash erstellen, maxfreq festlegen
foreach $num (@nums) {
$freq{$num}++;
if ($maxfreq < $freq{$num}) {
$maxfreq = $freq{$num}
}
}

# Hash ausgeben
@keys = sort { $a <=> $b } keys %freq;
for ($i = $maxfreq; $i > 0; $i--) {
foreach $num (@keys) {
$space = (length $num);
if ($freq{$num} >= $i) {
print( (" " x $space) . "*");
} else {
print " " x (($space) + 1);
}
if ($i == $maxfreq) {
$totalspace += $space + 1;
}
}
print "\n";
}
print "-" x $totalspace;
print "\n @keys\n\n";
}

Abgesehen davon, dass wir alle Häufigkeitsberechnungen in dieser Subroutine zusammengefaßt haben, hat sich an dem Vorgang selbst nicht viel geändert. Er ist nur etwas eigenständiger geworden.

Das war's schon. Listing 11.2 zeigt das gesamte Skript.

Listing 11.2: Das Skript statsmenue.pl

#!/usr/bin/perl -w

@nums = (); # Input aus Datei, eine Zahl pro Zeile
$choice = "";

# Hauptskript
&getinput();
while ($choice !~ /q/i) {
$choice = &printmenu();
SWITCH: {
$choice =~ /^1/ && do {
&printdata(); last SWITCH; };
$choice =~ /^2/ && do {
&countsum (); last SWITCH; };
$choice =~ /^3/ && do {
&maxmin(); last SWITCH; };
$choice =~ /^4/ && do {
&meanmed(); last SWITCH; };
$choice =~ /^5/ && do {
&printhist(); last SWITCH; };
}
}

# Input aus Datei lesen und dann sortieren
sub getinput {
my $count = 0;
while (<>) {
chomp;
$nums[$count] = $_;
$count++;
}
@nums = sort { $a <=> $b } @nums;
}

# das Menue. Beenden mit Q
sub printmenu {
my $in = "";
print "Was moechten Sie ausgeben? (Beenden mit Q): \n";
print "1. eine Liste der Zahlen \n";
print "2. die Anzahl und Summe der Zahlen\n";
print "3. die kleinste und die groesste Zahl\n";
print "4. den Durchschnitts- und den Medianwert\n";
print "5. ein Diagramm, wie oft jede Zahl vorkommt.\n";
while () {
print "\nIhre Auswahl --> ";
chomp($in = <STDIN>);
if ($in =~ /^\d$/ || $in =~ /^q$/i) {
return $in;
} else {
print "Ungueltige Eingabe. 1-5 oder Q, bitte.\n";
}
}
}

# die Daten ausgeben, zehn Zahlen pro Zeile
sub printdata {
my $i = 1;
print "die Zahlen: \n";
foreach $num (@nums) {
print "$num ";
if ($i == 10) {
print "\n";
$i = 1;
} else { $i++; }
}
print "\n\n";
}

# Anzahl und Summe der Elemente ausgeben
sub countsum {
print "Anzahl der Zahlen: ", scalar(@nums), "\n";
print "Summe der Zahlen: ", &sumnums(),"\n\n";
}

# Summe der Zahlen berechnen
sub sumnums {
my $sum = 0;
foreach $num (@nums) {
$sum += $num;
}
return $sum;
}

# kleinste und groesste Zahl ausgeben
sub maxmin {
print "Kleinste Zahl: $nums[0]\n";
print "Groesste Zahl: $nums[$#nums]\n\n";
}

# Durchschnitt und Median ausgeben
sub meanmed {
printf("Durchschnitt: %.2f\n", &sumnums() / scalar(@nums));
print "Median: $nums[@nums / 2]\n\n";
}

# Das Histogramm ausgeben. Haeufigkeits-Hash erstellen und ausgeben
sub printhist {
my %freq = ();
my $maxfreq = 0;
my @keys = ();
my $space = 0;
my $totalspace = 0;
my $num;
my $i;

# freq-Hash erstellen, maxfreq festlegen
foreach $num (@nums) {
$freq{$num}++;
if ($maxfreq < $freq{$num}) {
$maxfreq = $freq{$num}
}
}

# Hash ausgeben
@keys = sort { $a <=> $b } keys %freq;
for ($i = $maxfreq; $i > 0; $i--) {
foreach $num (@keys) {
$space = (length $num);
if ($freq{$num} >= $i) {
print( (" " x $space) . "*");
} else {
print " " x (($space) + 1);
}
if ($i == $maxfreq) {
$totalspace += $space + 1;
}
}
print "\n";
}
print "-" x $totalspace;
print "\n @keys\n\n";
}

Vertiefung

Wie immer habe ich Ihnen noch nicht alles gesagt und möchte Sie in diesem Abschnitt zumindest darauf aufmerksam machen.

In der perlsub-Manpage sind Perl-Subroutinen und wie Sie sie definieren, deklarieren und verwenden, detailliert beschrieben. Weil my ein Operator ist, finden Sie mehr Informationen über my in der perlfunc-Manpage (wir werden uns aber auch am Tag 13 im Zusammenhang mit Gültigkeitsbereichen noch einmal mit ihm befassen). Sehen Sie also in diese Seiten, wenn die folgenden Themen Sie genauer interessieren.

Die Klammern um Argumente weglassen

Beim Aufrufen von Perl-eigenen Funktionen können Sie die Klammern um die Argumente auch weglassen, wenn Perl dann noch verstehen kann, wo die Argumente anfangen und aufhören. Das gleiche gilt auch für Ihre eigenen Subroutinen, allerdings nur unter zwei weiteren Bedingungen:

Es ist Perl eigentlich gleichgültig, ob Sie eine Subroutine im Skript vor oder nach ihrem Aufruf deklarieren (in einigen Sprachen müssen Sie sie zuerst deklariert haben). Die Ausnahme von dieser Regel ist das Weglassen der Klammern.

Hier kommt der kleine, aber feine Unterschied zwischen Deklaration und Definition von Subroutinen ins Spiel. Jede Subroutinendefinition ist gleichzeitig auch ihre Deklaration; sagt also Perl: »Hallo, es gibt hier eine Subroutine mit diesem Namen.« Der dann folgende Block legt fest, was die Subroutine im einzelnen macht. Sie können eine Subroutine aber auch ohne diesen Block »vordeklarieren«:

sub meine_subroutine;

Dann weiß Perl, dass mit meine_subroutine eine Subroutine gemeint ist, und durchsucht das Skript nach ihrer Definition.

Eine auf diese Art vordeklarierte Subroutine können Sie auch ohne Klammern um die Argumente aufrufen, wenn die eigentliche Definition erst am Ende des Skripts steht (aber irgendwo muss sie stehen, vergessen Sie sie nicht).

Es ist unter Perl-Programmierern allerdings gängige Praxis, die Klammern immer zu setzen, ob sie unbedingt nötig sind oder nicht, weil Subroutinenaufruf mit Klammern leichter lesbar und weniger fehlerträchtig - also empfehlenswert - ist.

Mit @_ Argumente an Subroutinen übergeben

Wie Sie wissen, ist @_ das lokale Spezialarray einer Subroutine, das alle Argumente an diese Subroutine enthält. Sie können @_ aber nicht nur innerhalb, sondern auch außerhalb der Subroutine zum Festlegen der Argumente verwenden.

Wenn Sie @_ zum Beispiel im Hauptkörper Ihres Skripts ein paar Elemente zuweisen und dann eine vordeklarierte Subroutine ohne Argumente aufrufen, übergibt Perl dieser Subroutine das Array @_ mit den von Ihnen gesetzten Werten:

sub meine_subroutine;
@_ = ('dies', 'das', 'sonstwas');
&meine_subroutine; # uebergibt den aktuellen Wert von @_

Weil &meine_subroutine hier ohne Klammern, aber mit Präfix aufgerufen wird, übergibt Perl ihr die Elemente von @_ als Parameterliste. Das kann nützlich sein, wenn Sie zum Beispiel zehn verschiedene Subroutinen mit den gleichen Argumenten aufrufen möchten.

Bei verschachtelten Subroutinen funktioniert es genauso. Sie können die Parameter einer Subroutine (nämlich den aktuellen Inhalt von @_) einfach an die nächste Subroutine weitergeben. Beachten Sie aber, dass die Subroutine bereits deklariert sein muss und dass Sie ihr »selbst gefülltes« @_ überschreiben, sobald Sie die Subroutine mit irgendeinem Argument aufrufen, und sei es nur ein leeres Paar Klammern.

Anonyme Subroutinen

Anonyme Subroutinen sind Subroutinen ohne Namen, die ähnlich funktionieren wie die Pointer (Zeiger) auf Funktionen in C und sozusagen erst zur Laufzeit zum Leben erwachen. Mit anonymen Subroutinen können Sie Referenzen auf eine Subroutine erzeugen und über diese Referenzen auf sie zugreifen - aber das geht hier zu weit. Wir befassen uns mit anonymen Subroutinen am Tag 19, wenn wir Referenzen behandeln.

Prototypen

Ich habe am Anfang des Buches erwähnt, dass kleinere Perl-Updates in erster Linie Fehler beheben, doch manchmal auch ganze neue Features mitbringen. Eins dieser Features sind Prototypen, Bestandteil von Perl seit Version 5.003. Mit Prototypen können Sie Ihre Subroutinen so deklarieren, dass sie wie Perl-eigene Funktionen aussehen und arbeiten - das heißt, dass Sie für eine Subroutine Typ und Anzahl der Argumente festlegen können, statt sie jede x-beliebige Parameterliste entgegennehmen zu lassen.

Prototypen wirken sich nur auf Subroutinen aus, die ohne das &-Präfix aufgerufen werden. Sie können dieselbe Subroutine auch mit & aufrufen, nur wird der Prototyp dann ignoriert. Wofür Sie sich jeweils entscheiden, bleibt Ihrer Lust und Laune überlassen.

Eine Subroutine mit Prototyp deklarieren oder definieren Sie folgendermaßen:

sub NAME (PROTOTYP);   #nur (Vor-) Deklaration
sub NAME (PROTOTYP) #Deklaration mit anschließender Definition
{ ... # Definition
}

Wobei NAME hier für den Namen Ihrer Subroutine steht. Den PROTOTYP setzen Sie aus folgenden Sonderzeichen zusammen, die sich auf Anzahl und Typ der erlaubten Argumente repräsentieren:

So verlangt zum Beispiel eine mit dem Prototyp ($$) deklarierte Subroutine zwei Skalarvariablen als Argumente. Der Prototyp ($$;@) fordert zwei Skalare und eine optionale Liste6, genauer eine Arrayvariable (die ja mit einem @ beginnt).

So sehr Prototypen Ihnen auch gefallen mögen - bedenken Sie, dass sie nur in neueren Perl-Versionen implementiert sind. Das System, auf dem Sie Ihre Skripts laufen lassen, muss also mindestens Perl 5.003 installiert haben.

Mehr Informationen zu Prototypen finden Sie in der perlsub-Manpage.

Die Funktion caller

Eine Funktion, die ich im Kapitel »übergangen« habe, ist caller. Die Funktion caller gibt Informationen, von wo aus eine Subroutine aufgerufen wurde (das kann bei der Fehlersuche behilflich sein). Für mehr Details sehen Sie in die perlfunc-Manpage.

Zusammenfassung

Eine Subroutine ist eine Möglichkeit, häufig gebrauchten Code zusammenzufassen oder ein langes Skript in kleinere »Portionen« aufzuteilen. Heute haben Sie gelernt, wie Sie Subroutinen deklarieren, definieren, aufrufen und Werte in sie hinein und wieder heraus bekommen. In diesem Zusammenhang haben wir auch über den Gültigkeitsbereich von globalen und lokalen Variablen gesprochen.

Subroutinen definieren Sie mit dem Schlüsselwort sub, dem Namen der Subroutine und einem Block, der die auszuführenden Anweisungen enthält. Innerhalb dieses Blocks können Sie mit my lokale Variablen deklarieren, die nur für diese Subroutine sichtbar und verfügbar sind. An die übergebenen Argumente kommen Sie heran, indem Sie auf das lokale Spezialarray @_ zugreifen, und mit der Funktion return geben Sie einen Wert oder eine Wertliste aus der Subroutine zurück. Wenn es relevant ist, in welchem Kontext die Subroutine aufgerufen wurde, ermitteln Sie ihn mit der Funktion wantarray.

Sie rufen Subroutinen mit einem optionalen &-Präfix, dem Namen der Subroutine und den Argumenten in Klammern auf. Perl gibt alle Argumente in einer linearen, flachen Liste an das Array @_. Hashes und verschachtelte Listen werden dabei expandiert.

Fragen und Antworten

Frage:
Was ist der Unterschied zwischen Subroutinen und Funktionen?

Antwort:
Im Grunde gibt es keinen Unterschied, beide bezeichnen vom Konzept her dasselbe. Deswegen kann man die beiden Begriffe zur Unterscheidung zwischen bestimmten Arten von Funktionen/Subroutinen verwenden. Ich zum Beispiel unterscheide in diesem Buch zwischen den in Perl eingebauten und Ihren selbstdefinierten Funktionen.

Frage:
Subroutinenaufrufe mit & sehen irgendwie seltsam aus. Kann ich das & weglassen?

Antwort:
Ja, das &-Präfix ist optional. Ob Sie es verwenden oder nicht, liegt ganz allein bei Ihnen.

Frage:
Ich möchte zwei Arrays an eine Subroutine übergeben und auch zwei Arrays zurückbekommen. Aber die beiden Arrays werden auf dem Hin- und Rückweg in einer einzelnen Liste zusammengeschmissen. Wie halte ich sie auseinander?

Antwort:
Am besten machen Sie das mit Referenzen, und die besprechen wir am Tag 19. Eine andere Möglichkeit wäre, innerhalb der Subroutine einfach zwei globale Arrays zu verändern, anstatt ihr die Daten in einer Liste zu übergeben.

Frage:
Aber globale Variablen sind böse, schlecht und verwerflich.

Antwort:
Na, das kommt darauf an, mit wem Sie sprechen. Manche Probleme lassen sich in Perl durchaus am besten mit globalen Variablen lösen. Wenn Sie vorsichtig mit ihnen umgehen, sie »sauber« deklarieren und die Grenzen ihrer Gültigkeitsbereiche klar ziehen, dann kommen Sie um die Nachteile von globalen Variablen auch herum. Wir befassen uns übermorgen genauer mit Gültigkeitsbereichen.

Frage:
Ich verstehe das nicht, sind Perl-Subroutinen nun call-by-value oder call-by- reference?

Antwort:
Weil Perl keine formalen Parameter hat, ist das gar nicht so leicht zu beantworten. Prinzipiell ist die Übergabe der Parameterliste an die Subroutine eine Referenzübergabe (call-by-reference): Wenn Sie direkt auf @_ zugreifen und zum Beispiel eine Skalarvariable verändern, ändert sich ihr Wert auch außerhalb der Subroutine. Beachten Sie aber, dass Hashes und verschachtelte Arrays expandiert werden - bei Arrays haben Sie dann bestenfalls eine Referenzübergabe der Einzelteile, bei Hashes eine reine Wertübergabe (call-by-value). Am einfachsten ist, alle Elemente von @_ lokalen Variablen zuzuweisen und mit diesen weiterzuarbeiten. Das entspräche einer Call-by-value-Übergabe aller Argumente - Ihren Originalvariablen könnte nichts mehr passieren.

Workshop

Der Workshop enthält Quizfragen, die Ihnen helfen sollen, Ihr Wissen zu festigen, und Übungen, die Sie anregen sollen, das eben Gelernte umzusetzen und eigene Erfahrungen zu sammeln. Versuchen Sie, das Quiz und die Übungen zu beantworten und zu verstehen, bevor Sie zur Lektion des nächsten Tages übergehen.

Quiz

  1. Nennen Sie zwei Gründe, warum Subroutinen nützlich sind.
  2. Wie ruft man eine Subroutine auf?
  3. Spielt es eine Rolle, ob eine Subroutine vor ihrem Aufruf definiert wurde? Wo im Skript sollten Subroutinendefinitionen stehen?
  4. Welchen Wert gibt eine Subroutine zurück, wenn Sie ihn nicht mit return explizit festlegen?
  5. Ist der Wert, den return aus einer Subroutine zurückgibt, ein Skalar oder eine Liste?
  6. In welchen Teilen eines Perl-Skripts sind mit my deklarierte Variablen verfügbar?
  7. Was passiert, wenn Sie mit my eine lokale Variable deklarieren und es bereits eine gleichnamige globale Variable gibt?
  8. Was geschieht mit einem Hash, wenn man ihn an eine Subroutine übergibt?
  9. Wozu dient @_? Wo haben Sie Zugriff auf @_?
  10. Wie geben Sie Argumenten in einer Perl-Subroutine einen Namen?
  11. Was macht wantarray? Wozu könnten Sie es gebrauchen?

Übungen

  1. Schreiben Sie eine Subroutine, die nichts macht, als ihre Argumente auszugeben, jedes in einer eigenen Zeile mit Zeilennummer.
  2. Schreiben Sie eine Subroutine, die einen String entgegennimmt und in umgekehrter Wortreihenfolge zurückgibt (zum Beispiel - Beispiel zum).
  3. Schreiben Sie eine Subroutine, die eine Liste von Zahlen entgegennimmt und die entsprechenden Quadratzahlen zurückgibt. Wenn der String nichtnumerische Elemente enthält, löschen Sie diese aus der Rückgabeliste.
  4. FEHLERSUCHE: Was ist falsch an diesem Skript?
        @gemeinsam = &schnittmenge(@liste1, @liste2);

    sub schnittmenge {
    my (@eins, @zwei) = @_;
    my @final = ();
    OUTER: foreach my $el1 (@eins) {
    foreach my $el2 (@zwei) {
    if ($el1 eq $el2) {
    @final = (@final, $el1);
    next OUTER;
    }
    }
    }
    return @final;
    }
  5. FEHLERSUCHE: Und was ist mit diesem hier?
        $wie_oft= &suche(@input, $suchwort);

    sub suche {
    my (@in, $suchwort) = @_;
    my $count = 0;
    foreach my $str (@in) {
    while ($str =~ /$suchwort/og) {
    $count++;
    }
    }
    return $count;
    }
  6. Schreiben Sie eine Subroutine, die, wenn in Skalarkontext aufgerufen, eine Zeile von der Tastatur einliest und sie in umgekehrter Zeichenreihenfolge ausgibt. Wenn man sie in Listenkontext aufruft, soll sie mehrere Zeilen in eine Liste lesen und die Reihenfolge der Zeilen umkehren (die letzte Zeile zuerst und so weiter, die einzelnen Strings selbst sollen bleiben, wie sie sind).
  7. Nehmen Sie das Grafik-Extraktionsskript von gestern, und schreiben Sie es diesmal mit Subroutinen.

Antworten

Hier die Antworten auf die Workshop-Fragen aus dem vorigen Abschnitt.

Antworten zum Quiz

  1. Subroutinen sind aus mehreren Gründen nützlich:
  1. Man ruft Subroutinen mit einer der folgenden Formen auf:
        &subroutine();    # beides: &-Präfix und Klammern
    &subroutine(1,2) # mit Argumenten
    subroutine(1,2) # & ist optional
  2. Eine Subroutinendefinition hat folgende Form:
        sub NAME {
    # Körper der Subroutine
    }
  3. Nein, es spielt keine Rolle, ob eine Subroutine vor oder nach ihrem Aufruf definiert wird. In einigen Sonderfällen (wenn zum Beispiel die Klammern um die Argumente weggelassen werden) muss sie aber vor ihrem Aufruf wenigstens deklariert worden sein.
  4. Überall, wo im Skript eine normale Anweisung stehen kann, kann man auch eine Subroutine definieren. Üblich ist allerdings, alle Subroutinendefinitionen an den Anfang oder das Ende des Skripts zu stellen.
  5. Ohne ein explizites return gibt die Subroutine das Ergebnis der letzten Auswertung im Block zurück.
  6. Fangfrage! Ob return einen Skalar oder eine Liste zurückgibt, bestimmen Sie.
  7. My-Variablen sind nur in dem Block, in dem sie deklariert wurden, sichtbar und verfügbar, nirgendwo sonst - nicht einmal in von dort aufgerufenen Subroutinen.
  8. Mit my deklarierte Variablen verstecken gleichnamige globale Variablen. Erst wenn Perl den Gültigkeitsbereich der my-Variablen verläßt, sind ihre globalen »Namensvettern« wieder verfügbar.
  9. Alle Argumente an eine Subroutine werden als eine einzige lineare Liste übergeben (und kommen als solche zurück). Wie in jedem Listenkontext werden Arrays und Hashes in einer solchen Liste interpoliert - ein Hash also in seine einzelnen Schlüssel und Werte expandiert.
  10. @_ ist die Parameterliste der aktuellen Subroutine - das lokale Spezialarray, das alle Argumente an diese Subroutine enthält. Normalerweise greift man innerhalb der Subroutine darauf zu, aber man kann @_ auch außerhalb verwenden (siehe Abschnitt »Prototypen«).
  11. Perl kennt keine formalen Parameter. Wenn Sie Ihren Argumenten einen Namen geben möchten, müssen Sie die entsprechenden Elemente von @_ lokalen Variablen zuweisen (die Sie ja benennen können, wie Sie wollen). Allerdings entspricht das einer Call-by-value-Wertübergabe, Sie arbeiten also nur mit den Werten der Argumente weiter, nicht mit den Originalargumenten außerhalb der Subroutine.
  12. Die Funktion wantarray liefert wahr zurück, wenn die Subroutine in Listenkontext aufgerufen wurde. Mit ihr könnten Sie herausfinden, in welchem Kontext die Subroutine aufgerufen wurde, und je nachdem (ob Listen- oder Skalarkontext) ein entsprechendes Ergebnis zurückgeben.

Lösungen zu den Übungen

  1. Hier ist eine Antwort:
        sub print_argumente {
    my $zeile = 1;
    foreach my $arg (@_) {
    print "$zeile. $arg\n";
    $zeile++;
    }
    }
  2. Hier eine mögliche Antwort:
        sub string_umkehren {
    my @str = split(/\s+/, $_[0]);
    return join(" ", reverse @str);
    }
  3. Hier eine Antwort
        sub quadrate {
    my @ergebnis = ();
    foreach my $el (@_) {
    if ($el !~ /\D+/) {
    @ergebnis = (@ergebnis, $el**2);
    }
    }
    return @ergebnis;
    }
  4. Dieses Skript nimmt irrtümlicherweise an, dass die beiden Argumente liste1 und liste2 innerhalb der Subroutine noch zwei Listen (@eins und @zwei) seien. Sind sie aber nicht - all ihre Elemente werden fein säuberlich in einer Liste aneinandergereiht, an @_ weitergegeben und @eins zugewiesen. Für dieses Beispiel wären (momentan) zwei globale Variablen besser geeignet.
  5. Das Problem in diesem Skript liegt in der Reihenfolge der Argumente. Weil die - ich kann es gar nicht oft genug sagen - lineare Parameterliste zuerst alle Elemente von @input und dann das $suchwort enthält, weisen wir in der Subroutine auch das Suchwort der lokalen Variablen @input zu. Sie erinnern sich, wie gefräßig Listenvariablen auf der linken Seite einer Zuweisung sind - sie schlucken alle noch vorhandenen Elemente, und für das lokale $suchwort bleibt nichts mehr übrig. Tauschen Sie die Argumente einfach um, und weisen Sie zuerst das Suchwort und dann die Liste zu.
  6. Machen Sie's zum Beispiel so:
        sub rueckwaerts {
    my $in = "";
    if (wantarray()) { # Listenkontext
    my @inliste = ();
    while () {
    print 'Ihre Eingaben bitte: ';
    $in = <STDIN>;
    if ($in ne "\n") {
    @inliste = ($in, @inliste); # Reihenfolge
    # umkehren
    }
    else { last; }
    }
    return @inliste;
    }
    else { # Skalarkontext
    print 'Ihre Eingabe bitte: ';
    chomp($in = <STDIN>);
    return reverse $in;
    }
    }
  7. Was halten Sie hiervon:
        #!/usr/bin/perl -w

    $/ = ""; # Absatzeingabe-Modus

    while (<>) {
    while (/<IMG\s([^>]+)>/ig) {
    &bild_atts_ermitteln($1);
    }
    }

    sub bild_atts_ermitteln {
    my $roh = $_[0];
    my %atts = ();

    while ($roh =~ /([^\s=]+)\s*=\s*("([^"]+)"|[^\s]+\s*)/ig) {
    if (defined $3) {
    $atts{ uc($1) } = $3;
    } else { $atts{ uc($1)} = $2; }
    }
    if ($roh =~ /ISMAP/i) {
    $atts{'ISMAP'}= "Yes";
    }
    &print_atts(%atts);
    }

    sub print_atts {
    my %atts = @_;

    print '-' x 15;
    print "\nImage: $atts{'SRC'}\n";
    foreach my $key ("WIDTH", "HEIGHT",
    "BORDER", "VSPACE", "HSPACE",
    "ALIGN", "ALT", "LOWSRC", "ISMAP") {
    if (exists($atts{$key})) {
    $atts{$key} =~ s/[\s]*\n/ /g;
    print " $key: $atts{$key}\n";
    }
    }
    }


vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


123456

© Markt&Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH